#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <FS.h>

#define SYS_LED 2

struct Config {
	String DeviceName = "Arduino";
	String DeviceId = "1234567891234567";
	String Userid = "770702827";
	String Token = "197ace52cf9463c09d27983c87fa7961";

	//下面的无需修改
	String SSID;
	String Passwd;
	String Server = "easylinker.net";
	String ProtocolType = "TCP";
	int HeartTimestamp = 360;         //1 hour,at least 2 hour

	//是否Debug
	bool IsDebug = true;
};
Config _config;
WiFiClient client;

int _lastHeartActiveTime = 0;

bool LoadConfig() {
	File configFile = SPIFFS.open("/config.json", "r");
	if (!configFile) {
		Serial.println("Failed to open config file");
		return false;
	}
	StaticJsonDocument<1024> doc;
	DeserializationError error = deserializeJson(doc, configFile);
	if (error) {
		Serial.println("Failed to read config file.");
		return false;
	}
	_config.SSID = doc["SSID"] | "fail";
	_config.Passwd = doc["Passwd"] | "fail";
	if (_config.SSID == "fail" || _config.Passwd == "fail") {
		return false;
	}
	else {
		Serial.println("Load wifi config from spiffs successful.");
		Serial.print("Loaded ssid: ");
		Serial.println(_config.SSID);
		Serial.print("Loaded passwd: ");
		Serial.println(_config.Passwd);
		return true;
	}
}

bool SaveConfig() {
	DynamicJsonDocument doc(1024);
	JsonObject root = doc.to<JsonObject>();
	root["SSID"] = _config.SSID;
	root["Passwd"] = _config.Passwd;
	root["Server"] = _config.Server;
	root["Token"] = _config.Token;

	File configFile = SPIFFS.open("/config.json", "w");
	if (!configFile) {
		Serial.println("Failed to open config file for writing");
		return false;
	}
	if (serializeJson(doc, configFile) == 0) {
		Serial.println("Failed to write to file");
		return false;
	}
	return true;
}

bool ServerWatchDog() {
	if (WiFi.status() != WL_CONNECTED) {
		BaseConfigWifiConnect();
	}
	if (!client.connected()) {
		EasyLinkerTcpConnect();
	}
	Heart();
	return true;
}

void BaseConfigWifiConnect() {
	Serial.println("Use base config to connect wifi.");
	WiFi.mode(WIFI_STA);
	WiFi.begin(_config.SSID, _config.Passwd);
	//连接超时时间，30s后没有连接将会转入SmartConfig
	int timeout = 30000;
	while (WiFi.status() != WL_CONNECTED) {
		Serial.println("Wait to connect wifi...");
		digitalWrite(SYS_LED, LOW);
		delay(150);
		digitalWrite(SYS_LED, HIGH);
		delay(150);
		timeout = timeout - 300;
		if (timeout <= 0) {
			Serial.println("Wifi connect timeout, use smart config to connect...");
			SmartConfigWifiConnect();
			return;
		}
	}
	Serial.println("WiFi connected by base config.");
	Serial.print("SSID:");
	Serial.println(WiFi.SSID());
	Serial.print("IP Address:");
	Serial.println(WiFi.localIP());
	delay(100);
}

void SmartConfigWifiConnect()
{
	Serial.println("Use smart config to connect wifi.");
	WiFi.mode(WIFI_STA);
	WiFi.beginSmartConfig();
	while (1)
	{
		Serial.println("Wait to connect wifi...");
		digitalWrite(SYS_LED, LOW);
		delay(1000);
		digitalWrite(SYS_LED, HIGH);
		delay(1000);
		if (WiFi.smartConfigDone())
		{
			Serial.println("WiFi connected by smart config.");
			Serial.print("SSID:");
			Serial.println(WiFi.SSID());
			Serial.print("IP Address:");
			Serial.println(WiFi.localIP());
			_config.SSID = WiFi.SSID();
			_config.Passwd = WiFi.psk();
			if (!SaveConfig()) {
				Serial.println("Failed to save config");
			}
			else {
				Serial.println("Config saved");
			}
			break;
		}
	}
	delay(100);
}

void Heart() {
	int nowtime = millis() / 1000;
	if (nowtime - _lastHeartActiveTime > _config.HeartTimestamp) {
		String heartStr = "";
		StaticJsonDocument<256> doc;
		doc["Device"] = _config.DeviceName;
		doc["Uid"] = _config.Userid;
		doc["Key"] = _config.Token;
		doc["ProtocolType"] = _config.ProtocolType;
		doc["Type"] = "Heart";
		doc["Action"] = "Other";
		doc["MessageId"] = millis();
		doc["DeviceId"] = _config.DeviceId;
		doc["Time"] = 0;
		serializeJson(doc, heartStr);
		if (_config.IsDebug) {
			Serial.print("HeartStr Length:");
			Serial.println(heartStr.length());
			Serial.print("HeartStr String:");
			serializeJsonPretty(doc, Serial);
			Serial.println();
		}
		if (heartStr.length() <= 0) {
			Serial.println("Heart send failed. Heart string is null");
		}
		_lastHeartActiveTime = nowtime;
		Publish(heartStr);
	}
}

void Recive() {
	if (WiFi.status() != WL_CONNECTED || !client.connected())
	{
		return;
	}
	String payload = "";
	if (client.available())
	{
		//接收数据
		while (client.available())
		{
			payload += char(client.read());
			delay(1);
		}
	}
	if (payload.length() <= 0) {
		return;
	}
	//This is debug output
	if (_config.IsDebug)
	{
		Serial.print("Payload Length:");
		Serial.println(payload.length());
		Serial.print("Payload:");
		Serial.println(payload);
	}

	DynamicJsonDocument doc(1024);
	DeserializationError error = deserializeJson(doc, payload);
	if (error) {
		Serial.println("Failed to read payload");
		return;
	}
	JsonObject obj = doc.as<JsonObject>();
	String type = obj["Type"].isNull() ? "" : obj["Type"].as<String>();
	String messageId = obj["MessageId"].isNull() ? "" : obj["MessageId"].as<String>();

	if (type == "Success" || type == "Error") {
		if (type == "Success") {
			ErrorMessage(messageId, 0, "Success");
		}
		else {
			ErrorMessage(messageId, obj["Data"]["Code"].as<int>(), obj["Data"]["Message"].as<String>());
		}
	}
	else if (type == "Control") {
		//Control(String messageId, String propertiy, String value)
		if (obj["Data"].isNull()) {
			Control(messageId, obj["Action"].as<String>(), "", "");
		}
		else {
			String  propertiy = "", value = "";
			if (!obj["Data"]["Propertiy"].isNull()) {
				propertiy = obj["Data"]["Propertiy"].as<String>();
			}
			if (!obj["Data"]["Value"].isNull()) {
				value = obj["Data"]["Value"].as<String>();
			}
			Control(messageId, obj["Action"].as<String>(), propertiy, value);
		}
	}
	else if (type == "Query") {
		Query(messageId, obj["Action"].as<String>());
	}
	else if (type == "Msg") {
		if (obj["Data"].isNull() || obj["Data"]["Content"].isNull())
		{
			return;
		}
		Message(messageId, obj["Data"]["Content"].as<String>());
	}
}

void EasyLinkerTcpConnect() {
	//登陆状态
	bool isLogin = false;
	Serial.println("Beigin to connect to easylinker server.");
	while (!client.connected())
	{
		// If when connect server wifi disconnect,return
		if (WiFi.status() != WL_CONNECTED)
		{
			return;
		}
		Serial.println("Try to connect easylinker server...");
		digitalWrite(SYS_LED, LOW);
		delay(300);
		digitalWrite(SYS_LED, HIGH);
		delay(300);
		if (client.connect(_config.Server, 8883))
		{
			break;
		}
		delay(3000);
	}
	String loginStr = "";
	StaticJsonDocument<256> doc;
	doc["Device"] = _config.DeviceName;
	doc["Uid"] = _config.Userid;
	doc["Key"] = _config.Token;
	doc["ProtocolType"] = _config.ProtocolType;
	doc["Type"] = "Login";
	doc["Action"] = "Other";
	doc["MessageId"] = millis();
	doc["DeviceId"] = _config.DeviceId;
	doc["Time"] = 0;
	serializeJson(doc, loginStr);
	if (_config.IsDebug) {
		Serial.print("LoginStr Length:");
		Serial.println(loginStr.length());
		Serial.print("Login String:");
		serializeJsonPretty(doc, Serial);
		Serial.println();
	}
	if (loginStr.length() <= 0) {
		Serial.println("Login failed. Login string is null");
	}

	//登录
	while (!isLogin)
	{
		if (!client.connected()) {
			break;
		}
		Publish(loginStr);
		int resultTimeOut = 3000;  //发送登录请求后，等待3000ms时间。
		while (true)
		{
			if (client.available()) {
				break;
			}
			if (resultTimeOut <= 0) {
				break;
			}
			delay(1);
			resultTimeOut--;
		}
		if (client.available())
		{
			String loginResult = "";
			//透传数据
			while (client.available())
			{
				loginResult += char(client.read());
				delay(1);
			}
			if (loginResult.length() <= 0)
			{
				continue;
			}
			DynamicJsonDocument doc(1024);
			DeserializationError error = deserializeJson(doc, loginResult);
			if (error) {
				Serial.println("Login failed. Failed to read login result string.");
				return;
			}
			JsonObject obj = doc.as<JsonObject>();
			if (!obj["Type"].isNull() && obj["Type"].as<String>() == "Login") {
				if (!obj["Data"]["Code"].isNull() && (obj["Data"]["Code"].as<String>() == "SUCCESS" || obj["Data"]["Code"].as<String>() == "0")) {
					isLogin = true;
					Serial.println("Login Successful. ");
					break;
				}
				else {
					isLogin = false;
					Serial.println("Login failed. Please check you userid or key is right.");
				}
			}
			else if (!obj["Type"].isNull() && obj["Type"].as<String>() == "Error") {
				if (!obj["Data"]["Code"].isNull()) {
					Serial.println("Login error. " + obj["Data"]["Code"].as<String>());
				}
			}
			else {
				Serial.println("Login failed. Unknow response type.");
			}
			delay(3000);  //After 3000ms re send login message
		}
	}
	Serial.flush();
}

void ConnectServer() {
	// Connect wifi
	if (LoadConfig()) {
		BaseConfigWifiConnect();
	}
	else {
		SmartConfigWifiConnect();
	}
	// Connect easylinker server
	EasyLinkerTcpConnect();
	digitalWrite(SYS_LED, HIGH);
}

void Publish(String message) {
	if (message.length() != 0 && client.connected())
	{
		if (client.availableForWrite()) {
			const char *send = message.c_str();
			client.write(send);
		}
		client.flush();
	}
}

void ErrorMessage(String messageId, int code, String message) {
	Serial.print("Type:");
	if (code == 0) {
		Serial.println("Success");
	}
	else {
		Serial.println("Error");
	}
	Serial.print("MessageId:");
	Serial.println(messageId);
	Serial.print("Code:");
	Serial.println(code);
	Serial.print("Message:");
	Serial.println(message);
}

void Control(String messageId, String action, String propertiy, String value) {
	if (_config.IsDebug) {
		Serial.print("Type:");
		Serial.println("Control");
		Serial.print("MessageId:");
		Serial.println(messageId);
		Serial.print("Action:");
		Serial.println(action);
		Serial.print("Propertiy:");
		Serial.println(propertiy);
		Serial.print("Value:");
		Serial.println(value);
	}

	if (action == "TurnOn" || action == "TurnOff") {
		if (value == "On") {
			StaticJsonDocument<1024> doc;
			doc["Device"] = _config.DeviceName;
			doc["Uid"] = _config.Userid;
			doc["Key"] = _config.Token;
			doc["ProtocolType"] = _config.ProtocolType;
			doc["Type"] = "Control";
			doc["Action"] = action;
			doc["MessageId"] = messageId;
			doc["DeviceId"] = _config.DeviceId;
			doc["Time"] = 0;
			JsonObject data = doc.createNestedObject("Data");
			data["PowerState"] = value;

			String result = "";
			serializeJson(doc, result);
			if (_config.IsDebug) {
				Serial.print("Result Length:");
				Serial.println(result.length());
				Serial.print("Result String:");
				serializeJsonPretty(doc, Serial);
				Serial.println();
			}
			if (result.length() > 0) {
				Publish(result);
			}
		}
		else if (value == "Off") {
			StaticJsonDocument<1024> doc;
			doc["Device"] = _config.DeviceName;
			doc["Uid"] = _config.Userid;
			doc["Key"] = _config.Token;
			doc["ProtocolType"] = _config.ProtocolType;
			doc["Type"] = "Control";
			doc["Action"] = action;
			doc["MessageId"] = messageId;
			doc["DeviceId"] = _config.DeviceId;
			doc["Time"] = 0;
			JsonObject data = doc.createNestedObject("Data");
			data["PowerState"] = value;

			String result = "";
			serializeJson(doc, result);
			if (_config.IsDebug) {
				Serial.print("Result Length:");
				Serial.println(result.length());
				Serial.print("Result String:");
				serializeJsonPretty(doc, Serial);
				Serial.println();
			}
			if (result.length() > 0) {
				Publish(result);
			}
		}
	}
}

void Query(String messageId, String action) {
	if (_config.IsDebug) {
		Serial.print("Type:");
		Serial.println("Query");
		Serial.print("MessageId:");
		Serial.println(messageId);
		Serial.print("Action:");
		Serial.println(action);
	}
}

void Message(String messageId, String msg) {
	if (_config.IsDebug) {
		Serial.print("Type:");
		Serial.println("Query");
		Serial.print("MessageId:");
		Serial.println(messageId);
		Serial.print("Message:");
		Serial.println(msg);
	}
}

void setup() {
	Serial.begin(115200);
	//Set LED
	pinMode(SYS_LED, OUTPUT);
	//Mount FS
	Serial.println("Mounting FS...");
	if (!SPIFFS.begin()) {
		Serial.println("Failed to mount file system.");
		return;
	}
	//Set Hostname
	WiFi.hostname("#" + _config.DeviceName + "-EasyLinker");
	//Connect WIFI
	ConnectServer();
}

void loop() {
	//watch dog,if wifi or server disconnected, it will auto reconnect.
	ServerWatchDog();
}